package logic

import (
	"context"
	"fmt"
	"googee/common/uniqueid"
	"googee/common/utils"
	"googee/service/leaderboard/model"
	"googee/service/mqueue/job/internal/svc"
	"math/rand"
	"strings"
	"time"

	"github.com/LK4D4/trylock"
	"github.com/fatih/color"
	"github.com/hibiken/asynq"
	"github.com/zeromicro/go-zero/core/stores/sqlx"
)

// LeaderBoardSortHandler   shcedule billing to home business
type LeaderBoardSortHandler struct {
	svcCtx     *svc.ServiceContext
	LastUpdate time.Time
	mu         trylock.Mutex
	runtest    bool
}

func NewLeaderBoardSortHandler(svcCtx *svc.ServiceContext) *LeaderBoardSortHandler {
	return &LeaderBoardSortHandler{
		svcCtx:  svcCtx,
		runtest: true,
	}
}

// 桶里面最大和最小的积分
func (l *LeaderBoardSortHandler) getMinMaxScore(ctx context.Context, bid int64, batchid int64) (int64, int64, error) {
	querysql := `SELECT max(score) as max_score, min(score) as min_score FROM leaderboards_data 
		WHERE bid=? and batchid=? limit 1`
	var x buckMinAndMaxScore

	err := l.svcCtx.SqlConn.QueryRowCtx(ctx, &x, querysql, bid, batchid)
	if err != nil {
		return 0, 0, err
	}

	return x.MaxScore, x.MinScore, nil
}

func (l *LeaderBoardSortHandler) getDenseSize(ctx context.Context, bid, from_score, to_score int64, batchid int64) int64 {
	querysql := "SELECT COUNT(score) size FROM leaderboards_data WHERE bid=? AND ?<=score AND score<=? and batchid=?"

	var c buckCount
	err := l.svcCtx.SqlConn.QueryRowCtx(ctx, &c, querysql, bid, from_score, to_score, batchid)
	if err != nil {
		color.Red("error:%v", err)
		return 0
	}

	return c.Count
}

func (l *LeaderBoardSortHandler) getRankSize(ctx context.Context, bid, from_score, to_score int64, batchid int64) int64 {
	querysql := "SELECT COUNT(DISTINCT(score)) size FROM leaderboards_data WHERE bid=? AND ?<=score AND score<=? and batchid=?"

	var c buckCount
	err := l.svcCtx.SqlConn.QueryRowCtx(ctx, &c, querysql, bid, from_score, to_score, batchid)
	if err != nil {
		color.Red("error:%v", err)
		return 0
	}

	return c.Count
}

func (l *LeaderBoardSortHandler) clearBucket(ctx context.Context, bid int64, batchid int64) error {
	querysql := "delete from leaderboards_buckets where bid=? and batchid=?"

	_, err := l.svcCtx.SqlConn.ExecCtx(ctx, querysql, bid, batchid)
	if err != nil {
		return err
	}
	return nil
}

func (l *LeaderBoardSortHandler) bulkInsert(ctx context.Context, buck *[]*model.LeaderboardsBuckets) {
	query := `insert into leaderboards_buckets (id,bid,batchid,from_score,to_score,from_rank,to_rank,from_dense,to_dense) 
		values (?, ?, ?,?, ?,?,?,?,?)`
	bulkInserter, _ := sqlx.NewBulkInserter(l.svcCtx.SqlConn, query)

	for _, b := range *buck {
		bulkInserter.Insert(b.Id, b.Bid, b.Batchid, b.FromScore, b.ToScore, b.FromRank, b.ToRank, b.FromDense, b.ToDense)
	}
	bulkInserter.Flush()
}

func (l *LeaderBoardSortHandler) test(ctx context.Context) {
	color.Red("test")
	if l.runtest {
		return
	}
	color.Red("test run")
	l.runtest = true

	rand.Seed(time.Now().UnixNano())
	min := 100
	max := 1000000

	query := `insert into leaderboards_data (id,bid,batchid,user_id,score) values (?, ?, ?,?, ?)`
	bulkInserter, _ := sqlx.NewBulkInserter(l.svcCtx.SqlConn, query)
	for i := 0; i < 600000; i++ {
		{
			x := &model.LeaderboardsData{
				Id:      uniqueid.GenId(),
				Bid:     1,
				UserId:  int64(i + 1),
				Score:   int64(rand.Intn(max-min) + min),
				Batchid: 1655683200,
			}

			bulkInserter.Insert(x.Id, x.Bid, x.Batchid, x.UserId, x.Score)
		}

		{
			x := &model.LeaderboardsData{
				Id:      uniqueid.GenId(),
				Bid:     2,
				UserId:  int64(i + 1),
				Score:   int64(rand.Intn(max-min) + min),
				Batchid: 1655683200,
			}

			bulkInserter.Insert(x.Id, x.Bid, x.Batchid, x.UserId, x.Score)
		}

		if i%100 == 0 {
			bulkInserter.Flush()
		}

		// l.svcCtx.LeaderboardsDataModel.Insert(ctx, &model.LeaderboardsData{
		// 	Id:      int64(i + 1),
		// 	Bid:     1,
		// 	UserId:  int64(i + 1),
		// 	Score:   int64(rand.Intn(max-min) + min),
		// 	Batchid: 1655683200,
		// })

	}

	bulkInserter.Flush()
}

//  every one minute exec : if return err != nil , asynq will retry
func (l *LeaderBoardSortHandler) ProcessTask(ctx context.Context, _ *asynq.Task) error {

	fmt.Printf("shcedule sort job -----> every one minute exec ,time:%v ,l=%v\n", time.Now().Unix(), l)
	d := time.Since(l.LastUpdate)

	if l.mu.TryLock() {
		l.test(ctx)
		defer l.mu.Unlock()

		color.Green("Leader Board Sort")

		//计算周期不能太密集,暂时是1分钟对应移动一下桶里面的数据
		if d.Seconds() < 50 {
			return nil
		}

		//获得所有的排行榜
		boards, err := l.svcCtx.LeaderboardsModel.FindAll(ctx)
		if err != nil {
			color.Red("err1=%v", err)
			return nil
		}

		now := time.Now()
		for _, b := range boards {
			var batchid int64
			chunk_block := CHUNK_BLOCK

			cron := strings.Trim(b.Cron, " ")

			batchid = utils.PrevCronTime(cron, now).Unix()
			color.Red("cron=%v", cron)
			color.Red("batchid=%v", batchid)
			color.Red("bid=%v", b.Id)

			max_score, min_score, err := l.getMinMaxScore(ctx, b.Id, batchid)
			color.Green("max_score=%v", max_score)
			color.Green("min_score=%v", min_score)

			if err != nil {
				color.Red("err2-------=%v", err)
				continue
			}

			buckets := []*model.LeaderboardsBuckets{}
			start_time := time.Now().UnixNano() / 1e6

			var rank int64 = 1
			var dense int64 = 1

			to_score := max_score
			chunk := DEFAULT_SCORE_CHUNK
			from_score := to_score - chunk
			from_score = utils.MaxInt64(min_score, from_score)

			// color.Green("to_score=%v", to_score)
			// color.Green("chunk=%v", chunk)
			// color.Green("from_score=%v", from_score)

			for to_score >= from_score {
				var dense_size int64
				for {

					dense_size = l.getDenseSize(ctx, b.Id, from_score, to_score, batchid)
					// color.Blue("\n+++++++")
					// color.Green("count=%v", count)
					// color.Green("from_score=%v", from_score)
					// color.Green("to_score=%v", to_score)
					// color.Green("dense_size=%v", dense_size)
					// color.Green("chunk_block=%v", chunk_block)
					// color.Green("chunk=%v", chunk)
					// color.Blue("+++++++\n")

					if from_score <= 0 ||
						((int64(chunk_block/2) < dense_size) && dense_size <= chunk_block) || chunk == 1 {
						// color.Blue("\n *************")
						// color.Green("from_score=%v", from_score)
						// color.Green("to_score=%v", to_score)
						// color.Green("dense_size=%v", dense_size)
						// color.Green("chunk_block=%v", chunk_block)
						// color.Green("chunk=%v", chunk)
						// color.Blue("*************\n")
						break
					}

					if chunk_block/2 > dense_size {
						chunk += chunk / 2
					} else {
						chunk -= chunk / 2
					}

					from_score = to_score - chunk
					from_score = utils.MaxInt64(0, from_score)

					// color.Blue("\n -------------")
					// color.Green("from_score=%v", from_score)
					// color.Green("to_score=%v", to_score)
					// color.Green("dense_size=%v", dense_size)
					// color.Green("chunk_block=%v", chunk_block)
					// color.Green("chunk=%v", chunk)
					// color.Blue("-------------\n")
				}

				rank_size := l.getRankSize(ctx, b.Id, from_score, to_score, batchid)
				if rank_size > 0 {
					buckets = append(buckets, &model.LeaderboardsBuckets{
						Id:        uniqueid.GenId(),
						Bid:       b.Id,
						FromScore: from_score,
						ToScore:   to_score,
						FromRank:  rank,
						ToRank:    rank + utils.MaxInt64(rank_size-1, 0),
						FromDense: dense,
						ToDense:   dense + utils.MaxInt64(dense_size-1, 0),
						Batchid:   batchid,
					})

					to_score = from_score - 1
					from_score = to_score - chunk
					from_score = utils.MaxInt64(min_score, from_score)
					dense += dense_size
					rank += rank_size
				}

			}

			endtime_time := time.Now().UnixNano() / 1e6
			// for _, buck := range buckets {
			// 	s, _ := json.MarshalIndent(buck, "", "    ")
			// 	color.Blue(string(s))
			// }
			color.Red("计算排行榜ID:%v,消耗时间为:%v", b.Id, endtime_time-start_time)

			//清空之前的记录
			start_time = time.Now().UnixNano() / 1e6
			l.clearBucket(ctx, b.Id, batchid)
			endtime_time = time.Now().UnixNano() / 1e6
			color.Red("计算排行榜ID:%v,删除消耗时间为:%vms", b.Id, endtime_time-start_time)
			//插入最新的buckets
			start_time = time.Now().UnixNano() / 1e6
			l.bulkInsert(ctx, &buckets)
			endtime_time = time.Now().UnixNano() / 1e6
			color.Red("计算排行榜ID:%v,批量插入消耗时间为:%vms", b.Id, endtime_time-start_time)
		}

		l.LastUpdate = time.Now()
	}

	return nil
}
